# -*- coding: utf-8 -*-
# @Time     : 2024/3/12 14:25
# @Author   : Long-Long Qiu
# @FileName : pack_worker.py
# @Product  : PyCharm
# @description  : 进行pack计算的worker进程
# import packages
import time
import os
import json
from py3dbp import Packer, Bin, Item, Painter
import pickle
import math
import argparse
from functools import partial
from multiprocessing import Pool

def packing(box, items, radius_threshold1, radius_threshold2, pre_packing_cubes=True):

    # 预处理：过滤数量小于1的项
    items = list(filter(lambda x: x[4] > 0, items))
    # 1. 获取box的高度
    if type(box[0]) == list or type(box[0]) == tuple:
        # box_length = box[0][0]
        # box_width = box[0][1]
        # box_height = box[0][2]
        box_length = max([be[0] for be in box])
        box_width = max([be[1] for be in box])
        box_height = max([be[2] for be in box])
    elif type(box[0]) == Bin:
        # box_length = float(box[0].width)
        # box_width = float(box[0].height)
        # box_height = float(box[0].depth)
        box_length = float(max([be.width for be in box]))
        box_width = float(max([be.height for be in box]))
        box_height = float(max([be.depth for be in box]))
    else:
        box_length, box_width, box_height = 0, 0, 0

    cubes = list(filter(lambda x: x[5] != 0, items))    # 所有的非盘具
    items = list(filter(lambda x: x[5] == 0, items))    # 所有的盘具

    if sum([ie[4] for ie in items]) > 0:
        heaps = pack_cylinders_2(items)
        heaps = {f"heap-{index + 1}": heap for index, heap in enumerate(heaps)}
    else:
        heaps = {}

    # 2. 将货物进行打包预处理
    # 2.1 预打包盘货：将盘货items转成heaps
    # if sum([ie[4] for ie in items]) > 0:
    #     heaps = pack_cylinders(items, radius_threshold1, radius_threshold2, box_height)
    #     heaps = {f"heap-{index + 1}": heap for index, heap in enumerate(heaps)}
    # else:
    #     heaps = {}

    if pre_packing_cubes:   # 对cubes进行预打包处理
    # 2.2 预打包cubes：将cubes转成heaps
        pre_pack_cubes = pack_cubes2
        cube_heaps, other_cubes, finished = pre_pack_cubes(cubes, box_length, box_width, box_height)

        while not finished:  # 预打包未结束
            # 继续进行预打包
            new_cube_heaps, other_cubes, finished = pre_pack_cubes(other_cubes, box_length, box_width, box_height, start_index=len(cube_heaps))
            cube_heaps = {**cube_heaps, **new_cube_heaps}
        # 3. 装箱
        result = pack_heap(heaps, box, cubes=other_cubes, cube_heaps=cube_heaps)
        return {**heaps, **cube_heaps}, result
    else:
        result = pack_heap(heaps, box, cubes=cubes)
        return heaps, result


def pack_cylinders_2(all_items):
    results = []
    for items in all_items:
        for i in range(items[4]):
            results.append([[[items[0], items[1], items[2], items[3], 0, 0, 0, 'orange']]])
    return results


def pack_cubes2(cubes, box_width, box_height, box_depth, start_index=0):
    # 1. 将木箱、托盘、纸箱分开
    woodens = [cube for cube in cubes if cube[5] == 1]
    trays = [cube for cube in cubes if cube[5] == 2]
    cartoons = [cube for cube in cubes if cube[5] == 3]

    # box_width /= 5
    # box_height /= 5

    results = {}
    flag = True

    # 2. 对不同种类的货物分别做处理
    # 2.1 预打包木箱
    for wooden in woodens:
        pass

    # 2.2 预打包托盘
    other_trays = []
    # 优先在y轴方向上平铺，其次在z轴方向上堆叠，最后在x轴方向上平铺
    for index, tray in enumerate(trays):
        # 获取货物的尺寸和重量
        width, height, depth, weight = tray[0:4]
        # 确定沿着y轴和z轴能放几个货物
        y_layers_num = math.floor(box_height // height)
        z_layers_num = 1
        if width * height / 1000 / 1000 > 0.6 and 100 < weight < 600:
            z_layers_num = math.floor(box_depth // depth)
        # x_layers_num = math.floor(box_width // width) // 20
        x_layers_num = 1
        layers = [[] for _ in range(z_layers_num)]
        x, y, z = 0., 0., 0.
        num = tray[4]
        w, h, d = 0, 0, 0
        for i in range(num):
            layers[d].append([round(width, 2), round(height, 2), round(depth, 2),
                              round(weight, 2), round(x, 2), round(y, 2), round(z, 2)])

            if h == y_layers_num - 1:  # y轴铺满，往z轴堆叠或x轴平铺
                if d == z_layers_num - 1:  # y轴铺满且z轴也堆满了，则往x轴方向平铺
                    w += 1
                    h = 0
                    d = 0
                    if w >= x_layers_num and num > i:  # 车厢放不下
                        other_trays.append([width, height, depth, weight, num - i - 1, 2])
                        break
                else:  # y轴铺满而z轴未堆满，则往z轴方向堆叠
                    h = 0
                    d += 1

                x, y, z = w * width, h * height, d * depth

            else:  # y轴未铺满，沿着y轴方向继续平铺
                h += 1
                y = h * height
        results[f"tray-{index + start_index}"] = list(filter(lambda m: m != [], layers))

    # 2.3 预打包纸箱
    other_cartoons = []
    # 优先在y轴方向上平铺，其次在z轴方向上堆叠，最后在x轴方向上平铺
    for index, cartoon in enumerate(cartoons):
        # 获取货物的尺寸和重量
        width, height, depth, weight = cartoon[0:4]
        # 确定沿着y轴和z轴能放几个货物
        y_layers_num = math.floor(box_height // height)
        z_layers_num = math.floor(box_depth // depth)
        # x_layers_num = math.floor(box_width // width) // 20
        x_layers_num = 1
        layers = [[] for _ in range(z_layers_num)]
        x, y, z = 0., 0., 0.
        num = cartoon[4]
        w, h, d = 0, 0, 0
        for i in range(num):
            layers[d].append([round(width, 2), round(height, 2), round(depth, 2),
                              round(weight, 2), round(x, 2), round(y, 2), round(z, 2)])

            if h == y_layers_num - 1:   # y轴铺满，往z轴堆叠或x轴平铺
                if d == z_layers_num - 1:   # y轴铺满且z轴也堆满了，则往x轴方向平铺
                    w += 1
                    h = 0
                    d = 0
                    if w >= x_layers_num and num > i:   # 车厢放不下
                        other_cartoons.append([width, height, depth, weight, num - i - 1, 3])
                        break
                else:   # y轴铺满而z轴未堆满，则往z轴方向堆叠
                    h = 0
                    d += 1

                x, y, z = w * width, h * height, d * depth

            else:   # y轴未铺满，沿着y轴方向继续平铺
                h += 1
                y = h * height
        results[f"cartoon-{index + start_index}"] = list(filter(lambda m: m != [], layers))

    # if len(other_trays) > preMinNum or len(other_cartoons) > preMinNum:
    if other_trays or other_cartoons:
        flag = False
    return results, other_trays + woodens + other_cartoons, flag


def pack_heap(heaps, boxes, cubes=[], cube_heaps={}, use_box_height=True):
    """
    将heap装载到box中
    :param heaps:
    :param boxes:
    :param cubes:
    :param use_box_height: 是否使用bin的高度作为heap的高度(可以用于防止在heap上方继续堆叠)
    :return:
    """

    # 1. 初始化packer对象，并将box添加到packer中
    packer = Packer()
    for idx, b in enumerate(boxes):
        if type(b) == list or type(b) == tuple:
            bin = Bin(f"car-{idx}", (b[0], b[1], b[2]), b[3], corner=0, put_type=0, price=b[4])
            packer.addBin(bin)
        elif type(b) == Bin:
            packer.addBin(b)

    # 2. 添加cylinder的heap对象
    colors = ['orange', 'olive', 'green', 'red', 'blue', 'purple', 'yellow']
    for name, heap in heaps.items():
        index = int(name.split('-')[1])
        width = heap[0][-1][4] + heap[0][-1][0]
        height = heap[0][0][1]
        depth = [item for layer in heap for item in layer][-1][6] + heap[0][0][2]
        if use_box_height:
            # depth = float(packer.bins[0].depth)
            depth = max([float(ib.depth) for ib in packer.bins])
        weight = sum([item[3] for layer in heap for item in layer])

        # print('cylinder-heap', width, height, depth)
        packer.addItem(
            Item(partno=f'Box-{index + 1}', name=name, typeof='heap',
                 WHD=(width, height, depth), weight=weight, level=0, loadbear=100,
                 updown=False, color=colors[index % len(colors)]))

    # 3. 添加cube的heap对象
    for name, heap in cube_heaps.items():
        if not heap:
            continue
        level_map = {'wooden': 1, 'tray': 2, 'cartoon': 3}
        # width = heap[0][-1][0] + heap[0][-1][4]
        width = max([ie[0] + ie[4] for layer in heap for ie in layer])
        # height = heap[0][-1][1] + heap[0][-1][5]
        height = max([ie[1] + ie[5] for layer in heap for ie in layer])
        depth = heap[-1][0][2] + heap[-1][0][6]
        # weight = heap[0][0][3]
        weight = sum([ie[3] for layer in heap for ie in layer])
        kind, index = name.split('-')[0], int(name.split('-')[1])
        packer.addItem(Item(partno=f"CubeBox-{index + 1}", name=name, typeof='cube',
                            WHD=(width, height, depth), weight=weight, level=level_map[kind],
                            loadbear=100, updown=False, color=colors[index % len(colors)]))

    # 4. 添加额外的cubes(1为木箱，2为托盘，3为纸箱，使用item.level进行区分)
    cube_colors = {
        1: 'purple',    # 木箱为紫色
        2: 'cyan',  # 托盘蓝绿色
        3: 'gray'   # 纸箱为灰色
    }
    cube_num = 1
    for index, cube in enumerate(cubes):
        n = cube[4]
        for _ in range(n):
            packer.addItem(Item(partno=f'Box-{cube_num + len(heaps)}', name=f'cube{cube_num + len(heaps)}', typeof='cube', WHD=(cube[0], cube[1], cube[2]),
                 weight=cube[3], level=cube[5], loadbear=cube[3],
                 updown=False, color=cube_colors[cube[5]]))
            cube_num += 1

    # 5. 装箱
    packer.pack(
        bigger_first=False,
        distribute_items=False,
        fix_point=True,
        check_stable=True,
        support_surface_ratio=1.,
        number_of_decimals=2
    )
    # put order
    packer.putOrder()
    return packer

def pack_box_v1(boxes, items, radius1, radius2, pre_packing_cubes):
    index, box, box_v, box_w, price = boxes[0], boxes[1], boxes[2], boxes[3], boxes[4]
    heaps, pack_result = packing([box], items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
    return heaps, pack_result, box_v, box_w, index, price

def pack_box_v2(boxes, items, radius1, radius2, pre_packing_cubes):
    boxes_index, box, box_v, box_w = boxes[0], boxes[1], boxes[2], boxes[3]
    heaps, pack_result = packing(box, items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
    return heaps, pack_result, box_v, box_w, boxes_index



def packing_items_sbc(items, all_cars, result_path, log_path, config_parameters={}, user_ip='127.0.0.1', pre_packing_cubes=True, pre_min_num=20, top_num=5):
    start = time.time()
    # 1. 准备结果文件
    absolute_file_path = f'{result_path}/result.json'
    if not os.path.exists(absolute_file_path):
        with open(absolute_file_path, 'w') as fp:
            json.dump({
                'finished': False,
                'result': []
            }, fp)

    # 2. 获取货物、车辆等信息
    if sum([ie[4] for ie in items]) < pre_min_num:
        pre_packing_cubes = False
    # radius1 = int(config_parameters['dish']['second_dish_radius_1']) / 100
    # radius2 = int(config_parameters['dish']['second_dish_radius_2']) / 100
    radius1 = 500
    radius2 = 700
    cars = [(car['size'][0], car['size'][1], car['size'][2], car['loadbear'], car['price']) for car in all_cars]
    cars = sorted(cars, key=lambda x: x[4], reverse=True)

    ie_v, ie_w = 0, 0
    for ie in items:
        ie_w += ie[3] * ie[4]
        ie_v += ie[0] * ie[1] * ie[2] * ie[4]

    total_time = 0
    results = []
    # 3. 计算单车方案
    suc_num = 0
    all_boxes = []
    for index, b in enumerate(cars):
        box = Bin(f"car-{index}", (b[0], b[1], b[2]), b[3], 0, put_type=0, price=b[4])
        box_v = float(box.getVolume())
        box_w = float(box.max_weight)
        price = b[4]
        if box_v < ie_v or box_w < ie_w:
            continue
        all_boxes.append([index, box, box_v, box_w, price])

    # 3.1 尝试装箱
    start_time = time.time()  # get the current time
    # heaps, pack_result = packing([box], items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
    partial_func = partial(pack_box_v1, items=items, radius1=radius1, radius2=radius2, pre_packing_cubes=pre_packing_cubes)
    num_processes = 10
    with Pool(processes=num_processes) as pool:
        all_results = pool.map(partial_func, all_boxes)
    end_time = time.time()  # get the current time again
    total_time += end_time - start_time  # add the time taken to the total

    # 3.2 判断装箱是否成功
    for heaps, pack_result, box_v, box_w, index, price in all_results:
        if len(pack_result.bins[0].items) == pack_result.total_items:
            suc_num += 1
            # 3.3 计算空间利用率、载重利用率等信息
            space_ratio = ie_v / box_v
            weight_ratio = ie_w / box_w
            car_type = f"car{index}-{[a / 1000 for a in cars[index][0:3]]}"
            # 3.4 结果汇总
            results.append({
                'status': True,
                'bin_type': [car_type],
                'bin_num': [1],
                'space_ratio': space_ratio,
                'box_weight': box_w,
                'goods_weight': ie_w,
                'weight_ratio': weight_ratio,
                'price': price,
                'goods_volume': ie_v / 100 / 100 / 100,
                'bin_volume': box_v / 100 / 100 / 100,
                'heaps': str(pickle.dumps(heaps), encoding='latin1'),
                'packer': str(pickle.dumps(pack_result), encoding='latin1'),
                'car-num': 1
            })
            # 3.5 结果保存
            with open(absolute_file_path, 'w') as fp:
                json.dump({
                    'ts': int(time.time()),
                    'finished': False,
                    'result': results
                }, fp, ensure_ascii=False)


    total_time = 0

    # 4. 计算多车方案
    car_types = []      # 记录方案是否已经计算
    multi_cars = [[[car]] for car in cars]
    finished = False
    while not finished:
        if len(multi_cars[0][0]) + 1 >= 11:
            break
        # 4.1 生成方案
        print('*' * 30)
        print(f"开始生成{len(multi_cars[0][0]) + 1}车方案")
        print('*' * 30)
        for cars_index in range(len(multi_cars)):
            current_cars = multi_cars[cars_index]
            cars_temp = []
            for current_car in current_cars:
                for car_index, car in enumerate(cars):
                    if car_index > cars_index:
                        continue
                    cars_temp.append(current_car + [car])
            multi_cars[cars_index] = cars_temp

        # 4.2 方案排序
        multi_boxes = [car for c_cars in multi_cars for car in c_cars]
        multi_boxes = sorted(multi_boxes, key=lambda x: sum([c[4] for c in x]), reverse=True)

        # 4.3 计算方案
        print('*' * 30)
        print(f"开始计算{len(multi_cars[0][0])}车方案")
        print('*' * 30)
        current_suc, current_total = 0, 0

        all_boxes = []
        for boxes_index, boxes in enumerate(multi_boxes):
            bin_v = sum([be[0] * be[1] * be[2] for be in boxes])
            bin_w = sum([be[3] for be in boxes])
            if bin_v < ie_v or bin_w < ie_w:
                continue

            all_boxes.append([boxes_index, boxes, bin_v, bin_w])

        # 4.4 尝试装箱
        # heaps, pack_result = packing(boxes, items, radius1, radius2, pre_packing_cubes=pre_packing_cubes)
        num_processes = 10  # Set the desired number of processes
        start_time = time.time()  # get the current time
        # Create a new function with fixed parameters
        partial_func = partial(pack_box_v2, items=items, radius1=radius1, radius2=radius2, pre_packing_cubes=pre_packing_cubes)
        with Pool(processes=num_processes) as pool:
            all_results = pool.map(partial_func, all_boxes)
        end_time = time.time()  # get the current time again
        total_time += end_time - start_time  # add the time taken to the total

        for heaps, pack_result, bin_v, bin_w, boxes_index in all_results:
            bin_type, bin_num = [], []
            car_type = []
            for bi in pack_result.bins:
                bin_type.append(f"car-{str([float(bi.width) / 1000, float(bi.height) / 1000, float(bi.depth) / 1000])}")
                car_type.append(str(bi.width))
                bin_num.append(1)
            car_type = sorted(car_type, key=lambda x: x)
            car_type = ','.join(car_type)
            if car_type not in car_types:
                # 4.5 判断装箱是否成功
                status = sum([len(ie.items) for ie in pack_result.bins]) == pack_result.total_items
                if status:
                    current_suc += 1
                    # 4.6 计算空间利用率、载重利用率等信息
                    space_ratio = ie_v / bin_v
                    weight_ratio = ie_w / bin_w
                    suc_num += 1
                    print('成功！！！', car_type)
                    # 4.7 追加结果
                    car_types.append(car_type)
                    results.append({
                        'status': True,
                        'bin_type': bin_type,
                        'bin_num': bin_num,
                        'space_ratio': space_ratio,
                        'box_weight': bin_w,
                        'goods_weight': ie_w,
                        'weight_ratio': weight_ratio,
                        'price': sum([bi.price for bi in pack_result.bins]),
                        'goods_volume': ie_v / 100 / 100 / 100,
                        'bin_volume': bin_v / 100 / 100 / 100,
                        'color': {},
                        'heaps': str(pickle.dumps(heaps), encoding='latin1'),
                        'packer': str(pickle.dumps(pack_result), encoding='latin1'),
                        'car-num': len(bin_type)
                    })
                    # 4.8 结果保存
                    with open(absolute_file_path, 'w') as fp:
                        json.dump({
                            'ts': int(time.time()),
                            'finished': False,
                            'result': results
                        }, fp, ensure_ascii=False)
                    if suc_num >= top_num:
                        finished = True

                else:
                    if 0.1 and current_total > 20:
                        break
            else:
                pass
                # print('重复')
    # print('22222222222222222222222222222222222222222222222222222222222222222222222222222222222222')
    # print(total_time)

    if finished:
        print('装箱进程结束！！！')
    # 5. 更改结果状态
    with open(absolute_file_path, 'r') as fp:
        data = json.load(fp)
        data['finished'] = True
    with open(absolute_file_path, 'w') as fp:
        json.dump(data, fp, ensure_ascii=False)

    # 6. 日志
    end = time.time()
    print(f"装箱耗时{round(end - start, 3)}秒")
    log_content = {
        'url': '/pack',
        'user_ip': user_ip,
        'start_time': int(start),
        'time': round(end - start, 3),
        'goods': items,
        'cars': all_cars,
        'config': config_parameters,
        'result': results
    }
    with open(log_path, 'a') as fp:
        fp.write(json.dumps(log_content, ensure_ascii=False) + '\n')


parser = argparse.ArgumentParser(description='启动pack_worker进程')
if __name__ == '__main__':
    # parser.add_argument('--items', type=str, default='[]', help='货物')
    # parser.add_argument('--cars', type=str, default='{}', help='车辆')
    # parser.add_argument('--resultPath', type=str, default='./results/1', help='session结果保存路径')
    # parser.add_argument('--logPath', type=str, default='./pack.log', help='日志保存路径')
    # parser.add_argument('--configParameters', type=str, default='{}', help='算法配置项')   # 暂时不用
    # parser.add_argument('--userIp', type=str, default='127.0.0.1', help='请求者ip地址')
    #
    # args = parser.parse_args()
    # items = json.load(open(args.items, 'r'))
    # # items = json.loads(args.items)
    # cars = json.load(open(args.cars, 'r'))
    # # cars = json.loads(args.cars)
    # configParameters = json.loads(args.configParameters)
    #
    # print('parameters')
    # print('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$')
    # print(items)
    # print(cars)
    # print(args.resultPath)
    # print(args.logPath)
    # print(configParameters)
    # print(args.userIp)
    # print('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$')
    # packing_items(items, cars, args.resultPath, args.logPath, configParameters, user_ip=args.userIp)

    items = [[1000, 1000, 600, 620, 80, 2], [1000, 1000, 1000, 416, 32, 2], [1000, 1000, 800, 315, 1, 2], [1000, 1000, 800, 255, 1, 2]]
    cars = [{'loadbear': 3000, 'price': 16834, 'size': [4200, 1900, 2500]}, {'loadbear': 10000, 'price': 30301, 'size': [6800, 2400, 3000]}, {'loadbear': 13000, 'price': 30862, 'size': [7600, 2400, 3000]}, {'loadbear': 18000, 'price': 34229, 'size': [9600, 2400, 3000]}, {'loadbear': 30000, 'price': 44891, 'size': [13500, 2400, 3000]}, {'loadbear': 35000, 'price': 50502, 'size': [17500, 3000, 3000]}]
    resultPath = './results_pack/55dca2464acd47550e25c6cfc0377398'
    logPath = './pack.log'
    configParameters = {}
    user_ip = '127.0.0.1'
    packing_items_sbc(items, cars, resultPath, logPath, configParameters, user_ip=user_ip)

